package com.weaver.qfengx.sdk.moxueyuan.utils;

import cn.hutool.core.codec.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class MoxueyuanEncryptor {

    private String token;
    private byte[] aesKey;
    private String corpId;

    private static final Integer AES_ENCODE_KEY_LENGTH = 43;
    private static final Integer RANDOM_LENGTH = 16;
    private static final Charset CHARSET = Charset.forName("utf-8");



    public MoxueyuanEncryptor(String token, String encodingAesKey, String corpId) {
        if ((encodingAesKey == null) || (encodingAesKey.length() != AES_ENCODE_KEY_LENGTH && encodingAesKey.length() != RANDOM_LENGTH)) {
            throw new RuntimeException("aeskey 长度非法");
        }
        this.token = token;
        this.aesKey = Base64.decode(aesKey + "=");
        this.corpId = corpId;
    }

    public Map<String, String> getEncryptedMap(String plaintext) {
        String nonce = new Random().nextInt(Integer.MAX_VALUE) + "";
        long timeStamp = System.currentTimeMillis();
        String encrypt = encrypt(getRandomStr(RANDOM_LENGTH), plaintext);
        String signature = getSignature(this.token, String.valueOf(timeStamp), nonce, encrypt);
        Map<String, String> resultMap = new HashMap<>();
        resultMap.put("msg_signature", signature);
        resultMap.put("encrypt", encrypt);
        resultMap.put("timeStamp", String.valueOf(timeStamp));
        resultMap.put("nonce", nonce);
        return resultMap;
    }

    /**
     * 加、解密时 byte不足两位则进行补零处理
     *
     * @param token
     * @param timestamp
     * @param nonce
     * @param encrypt
     * @return
     */
    public String getSignature(String token, String timestamp, String nonce, String encrypt) {
        try {
            String[] array = {token, timestamp, nonce, encrypt};
            Arrays.sort(array);
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 4; i++) {
                sb.append(array[i]);
            }
            String str = sb.toString();
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(str.getBytes());
            byte[] digest = md.digest();

            StringBuilder hexstr = new StringBuilder();
            String shaHex;
            for (byte b : digest) {
                shaHex = Integer.toHexString(b & 0xFF);
                if (shaHex.length() < 2) {
                    hexstr.append(0);
                }
                hexstr.append(shaHex);
            }
            return hexstr.toString();
        } catch (Exception e) {
            throw new RuntimeException("生成签名失败");
        }
    }

    public static String getRandomStr(int count) {
        String base = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < count; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    public static byte[] int2Bytes(int count) {
        byte[] byteArr = new byte[4];
        byteArr[3] = ((byte) (count & 0xFF));
        byteArr[2] = ((byte) (count >> 8 & 0xFF));
        byteArr[1] = ((byte) (count >> 16 & 0xFF));
        byteArr[0] = ((byte) (count >> 24 & 0xFF));
        return byteArr;
    }

    public static int bytes2int(byte[] byteArr) {
        int count = 0;
        for (int i = 0; i < 4; i++) {
            count <<= 8;
            count |= byteArr[i] & 0xFF;
        }
        return count;
    }

    private String encrypt(String random, String plaintext) {
        try {
            byte[] randomBytes = random.getBytes(CHARSET);
            byte[] plainTextBytes = plaintext.getBytes(CHARSET);
            byte[] lengthByte = int2Bytes(plainTextBytes.length);
            byte[] corpidBytes = this.corpId.getBytes(CHARSET);
            ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
            byteStream.write(randomBytes);
            byteStream.write(lengthByte);
            byteStream.write(plainTextBytes);
            byteStream.write(corpidBytes);
            byte[] padBytes = PKCS7Padding.getPaddingBytes(byteStream.size());
            byteStream.write(padBytes);
            byte[] unencrypted = byteStream.toByteArray();
            byteStream.close();
            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");

            //不足位数补零操作
            if (this.aesKey.length < 16) {
                this.aesKey = postscape(this.aesKey);
            }

            SecretKeySpec keySpec = new SecretKeySpec(this.aesKey, "AES");
            IvParameterSpec iv = new IvParameterSpec(this.aesKey, 0, 16);
            System.out.println("iv偏移量：" + bytesToHexString(iv.getIV()));

            cipher.init(1, keySpec, iv);
            byte[] encrypted = cipher.doFinal(unencrypted);
            return Base64.encode(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("加密失败");
        }
    }

    /**
     * 不足16位的 byte数据进行补零
     *
     * @param data 原byte数据
     * @return 处理成16位byte[]后的数据
     */
    private byte[] postscape(byte[] data) {
        byte[] bytes = new byte[16];
        for (int i = 0; i < bytes.length; i++) {
            if (i < data.length) {
                bytes[i] = data[i];
            } else {
                bytes[i] = 0;
            }
        }
        return bytes;
    }

    public static String bytesToHexString(byte[] bArr) {
        if (bArr == null) {
            return null;
        }
        StringBuilder sb = new StringBuilder(bArr.length);
        String sTmp;
        for (byte b : bArr) {
            sTmp = Integer.toHexString(0xFF & b);
            if (sTmp.length() < 2)
                sb.append(0);
            sb.append(sTmp);
        }
        return sb.toString();
    }

}
